/* Copyright (c) 2009, Michael Patraw
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * The name of Michael Patraw may not be used to endorse or promote
 *       products derived from this software without specific prior written
 *       permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Michael Patraw ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Michael Patraw BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "player.h"

#include <stdlib.h>
#include <string.h>

#include "ai.h"
#include "at.h"
#include "board.h"
#include "display.h"
#include "load.h"
#include "snarl.h"

/* Turn order.
 */
static player_t * _first = NULL; /* Player who acts first. */
static player_t * _last = NULL;
static player_t * _cur = NULL;   /* The current player's turn. */
static int _num_players = 0;

/* Returns AtColor based on team name.
 */
static AtColor _Player_GetTeamColor(int team);

/* Returns true if a player is at the location.
 */
static bool _Player_AtLocation(int x, int y);

player_t * Player_Create(board_t * board, int x, int y, int team,
        const char * name, int controller)
{
    player_t * player = (player_t *)malloc(sizeof(player_t));

    if (player) {
        player->board = board;
        player->name = name;
        player->controller = controller;
        player->x = x;
        player->y = y;
        player->team = team;
        player->num_imm_objects = 1;
        player->_next_player = NULL;
    }

    return player;
}

void Player_Destroy(player_t * player)
{
    if (player)
        free(player);
}

void Player_DestroyAll(void)
{
    player_t * iter = _first, * temp;
    
    do {
        if (iter) {
            temp = iter->_next_player;
            Player_Destroy(iter);
            iter = temp;
        }
    } while (iter != _first);
    
    _first = NULL;
    _last = NULL;
    _cur = NULL;
    _num_players = 0;
}

void Player_Loses(player_t * player, const char * reason)
{
    Player_RemoveFromTurnSequence(player);
}

bool Player_CurrentCheckIfLost(void)
{
    /* Check for win conditions. */
    if (_cur) {
        /* Squished. */
        if (BOARD_SQUARE(_cur->board, _cur->x, _cur->y) != HAS_NOTHING) {
            Player_Loses(_cur, "squished");
            return true;
        }

        /* Trapped. */
        else if (Board_ValidDirections(_cur->board, _cur->x, _cur->y) == 0) {
            Player_Loses(_cur, "trapped");
            return true;
        }
    }
    
    return false;
}

void Player_Draw(player_t * player, unsigned int win_id)
{
    int px, py;
    AtColor color;
    AtWindow * win = Display_RetrieveWindow(win_id);

    if (player == NULL)
        return;

    color = _Player_GetTeamColor(player->team);

    px = player->x;
    py = player->y;

    atWindowDrawChar(win, px, py, color, atWindowGetBgColor(win), PLAYER_TILE);
}

void Player_DrawEveryone(unsigned int win_id)
{
    player_t * iter = _first;
    
    do {
        if (iter) {
            Player_Draw(iter, win_id);
            iter = iter->_next_player;
        }
    } while (iter != _first);
}

bool Player_MoveBy(player_t * player, int dx, int dy)
{
    if (player != NULL && player->board != NULL) {
        int dir = Board_GetDirection(dx, dy);

        if (!(Board_ValidDirections(player->board, player->x, player->y) & dir))
            return false;
        
        if (_Player_AtLocation(player->x + dx, player->y + dy))
            return false;

        Board_PushDirection(player->board, player->x, player->y, dir);
        player->x += dx;
        player->y += dy;

        return true;
    }

    return false;
}

bool Player_MoveTo(player_t * player, int x, int y)
{
    if (player != NULL && player->board != NULL) {
        if (!Board_BadLocation(player->board, x, y))
            return false;
        
        if (_Player_AtLocation(x, y))
            return false;

        player->x = x;
        player->y = y;

        return true;
    }

    return false;
}

bool Player_CurrentMoveBy(int dx, int dy)
{
    return Player_MoveBy(_cur, dx, dy);
}

bool Player_CurrentMoveTo(int x, int y)
{
    return Player_MoveTo(_cur, x, y);
}

bool Player_PlaceObject(player_t * player, int dx, int dy)
{
    if (!Board_BadLocation(player->board, player->x + dx, player->y + dy)) {
        BOARD_SQUARE(player->board, player->x + dx, player->y + dy) = HAS_OBJECT;
        return true;
    }
    return false;
}

bool Player_PlaceImmovableObject(player_t * player, int dx, int dy)
{
    if (!Board_BadLocation(player->board, player->x + dx, player->y + dy) &&
        player->num_imm_objects > 0) {
        
        BOARD_SQUARE(player->board, player->x + dx, player->y + dy) = HAS_IMM_OBJECT;
        player->num_imm_objects -= 1;
        return true;
    }
    return false;
}

bool Player_CurrentPlaceObject(void)
{
    if (_cur) {
        AtKey input;

        while (atIsRunning()) {
            Snarl_BeginGameUpdate();
            atDrawString(MESSAGE_X, MESSAGE_Y, ATC_RED,
                atGetBgColor(), "Which direction?");
            Snarl_EndGameUpdate(); /* Stay in game loop. */

            input = atGrabKey();

            switch (input.key) {
                case ATK_UP:
                    if (!Board_BadLocation(_cur->board, _cur->x, _cur->y - 1)) {
                        BOARD_SQUARE(_cur->board, _cur->x, _cur->y - 1) = HAS_OBJECT;
                        return true;
                    }
                    break;
                case ATK_DOWN:
                    if (!Board_BadLocation(_cur->board, _cur->x, _cur->y + 1)) {
                        BOARD_SQUARE(_cur->board, _cur->x, _cur->y + 1) = HAS_OBJECT;
                        return true;
                    }
                    break;
                case ATK_LEFT:
                    if (!Board_BadLocation(_cur->board, _cur->x - 1, _cur->y)) {
                        BOARD_SQUARE(_cur->board, _cur->x - 1, _cur->y) = HAS_OBJECT;
                        return true;
                    }
                    break;
                case ATK_RIGHT:
                    if (!Board_BadLocation(_cur->board, _cur->x + 1, _cur->y)) {
                        BOARD_SQUARE(_cur->board, _cur->x + 1, _cur->y) = HAS_OBJECT;
                        return true;
                    }
                    break;
                case ATK_ESCAPE:
                    return false;
                    break;
                default:
                    break;
            }
        }

        return true;
    }
    return false;
}

bool Player_CurrentPlaceImmovableObject(void)
{
    if (_cur && _cur->num_imm_objects > 0) {
        AtKey input;

        while (atIsRunning()) {
            Snarl_BeginGameUpdate();
            atDrawString(MESSAGE_X, MESSAGE_Y, ATC_RED,
                atGetBgColor(), "Which direction?");
            Snarl_EndGameUpdate(); /* Stay in game loop. */

            input = atGrabKey();

            switch (input.key) {
                case ATK_UP:
                    if (!Board_BadLocation(_cur->board, _cur->x, _cur->y - 1)) {
                        BOARD_SQUARE(_cur->board, _cur->x, _cur->y - 1) = HAS_IMM_OBJECT;
                        --(_cur->num_imm_objects);
                        return true;
                    }
                    break;
                case ATK_DOWN:
                    if (!Board_BadLocation(_cur->board, _cur->x, _cur->y + 1)) {
                        BOARD_SQUARE(_cur->board, _cur->x, _cur->y + 1) = HAS_IMM_OBJECT;
                        --(_cur->num_imm_objects);
                        return true;
                    }
                    break;
                case ATK_LEFT:
                    if (!Board_BadLocation(_cur->board, _cur->x - 1, _cur->y)) {
                        BOARD_SQUARE(_cur->board, _cur->x - 1, _cur->y) = HAS_IMM_OBJECT;
                        --(_cur->num_imm_objects);
                        return true;
                    }
                    break;
                case ATK_RIGHT:
                    if (!Board_BadLocation(_cur->board, _cur->x + 1, _cur->y)) {
                        BOARD_SQUARE(_cur->board, _cur->x + 1, _cur->y) = HAS_IMM_OBJECT;
                        --(_cur->num_imm_objects);
                        return true;
                    }
                    break;
                case ATK_ESCAPE:
                    return false;
                    break;
                default:
                    break;
            }
        }

        return true;
    }
    return false;
}

void Player_AddToTurnSequence(player_t * player)
{
    if (_first == NULL) {
        _first = player;
        _last = player;
        player->_next_player = _first;
    }
    else {
       _last->_next_player = player;
       _last = player;
       _last->_next_player = _first;
    }
    
    ++_num_players;
}

void Player_RemoveFromTurnSequence(player_t * player)
{
    player_t * prev = _last, * iter = _first;
    
    if (player == NULL)
        return;
    
    if (_num_players == 1) {
        Player_ClearTurnSequence();
        return;
    }
    
    do {
        if (iter && prev) {
            if (iter == player) {
                if (_cur == iter)
                    _cur = iter->_next_player; /* Move the turn. */
                prev->_next_player = iter->_next_player;
                if (iter == _first)
                    _first = _first->_next_player;
                else if (iter == _last)
                    _last = _last->_next_player;
                --_num_players;
                return;
            }
            
            prev = iter;
            iter = iter->_next_player;
        }
        else {
            break;
        }
    } while (iter != _first);
}

void Player_ClearTurnSequence(void)
{
    _first = NULL;
    _last = NULL;
    _cur = NULL;
    _num_players = 0;
}

player_t * Player_NextTurn(void)
{
    if (_cur == NULL)
        _cur = _first;
    else
        _cur = _cur->_next_player;

    if (_cur && _cur->controller != HUMAN_CONTROLLED)
        if (!Player_CurrentCheckIfLost())
            Ai_Run(_cur);

    return _cur;
}

bool Player_IsFirst(player_t * player)
{
    if (player == NULL || _first == NULL)
        return false;
    return player == _first;
}

bool Player_IsLast(player_t * player)
{
    if (player == NULL || _last == NULL)
        return false;
    return player == _last;
}

bool Player_IsCurrentTurn(player_t * player)
{
    return player == _cur;
}

bool Player_CurrentIsAi(void)
{
    if (!_cur)
        return false;
    return _cur->controller != HUMAN_CONTROLLED;
}

void Player_DrawInformation(unsigned int win_id)
{
    AtWindow * win;
    AtColor color;
    size_t len;
    
    win = Display_RetrieveWindow(win_id);
    
    if (_cur) {
        color = _Player_GetTeamColor(_cur->team);
        len = strlen("It's ");
        atWindowDrawString(win, 0, 0, ATC_WHITE, atWindowGetBgColor(win),
            "It's ");
        atWindowDrawString(win, len, 0, color, atWindowGetBgColor(win),
            _cur->name);
        len = strlen("It's ") + strlen(_cur->name);
        atWindowDrawString(win, len, 0, ATC_WHITE, atWindowGetBgColor(win),
            "'s turn!");
    }
}

static AtColor _Player_GetTeamColor(int team) {
    switch (team) {
        case BLUE_TEAM:
            return ATC_BLUE;
            break;
        case GREEN_TEAM:
            return ATC_GREEN;
            break;
        default:
            return ATC_WHITE;
            break;
    }
}

static bool _Player_AtLocation(int x, int y)
{
    player_t * iter = _first;
    
    do {
        if (iter) {
            if (iter->x == x && iter->y == y)
                return true;
            iter = iter->_next_player;
        }
    } while (iter != _first);
    
    return false;
}
